---
description: Use the Nitric framework to easily build and deploy Go REST APIs for AWS, Azure or GCP
title_seo: Building your first API with Go and Nitric
tags:
  - API
  - Key Value Store
languages:
  - go
published_at: 2023-08-11
updated_at: 2025-01-06
---

# Building your first API with Nitric

## What we'll be doing

1. Use Nitric to create an API to create and update profiles
2. Create handlers for the following API operations

| **Method** | **Route**      | **Description**                  |
| ---------- | -------------- | -------------------------------- |
| `GET`      | /profiles/[id] | Get a specific profile by its Id |
| `GET`      | /profiles      | List all profiles                |
| `POST`     | /profiles      | Create a new profile             |
| `DELETE`   | /profiles/[id] | Delete a profile                 |
| `PUT`      | /profiles/[id] | Update a profile                 |

3. Run locally for testing
4. Deploy to a cloud of your choice
5. (Optional) Add handlers for the following API operations

| **Method** | **Route**                    | **Description**                   |
| ---------- | ---------------------------- | --------------------------------- |
| `GET`      | /profiles/[id]/image/upload  | Get a profile image upload URL    |
| `GET`      | profiles/[id]/image/download | Get a profile image download URL  |
| `GET`      | profiles/[id]/image/view     | View the image that is downloaded |

## Prerequisites

- [Go](https://go.dev/dl/)
- The [Nitric CLI](/get-started/installation)
- An [AWS](https://aws.amazon.com), [GCP](https://cloud.google.com) or [Azure](https://azure.microsoft.com) account (_your choice_)

## Getting started

We'll start by creating a new project for our API.

```bash
nitric new my-profile-api go-starter
```

Next, open the project in your editor of choice.

```bash
cd my-profile-api
```

Make sure all dependencies are resolved:

```bash
go mod tidy
```

The scaffolded project should have the following structure:

```text
+--services/
|  +-- hello/
|      +-- main.go
+--nitric.yaml
+--go.mod
+--go.sum
+--golang.dockerfile
+--.gitignore
+--README.md
```

You can test the project to verify everything is working as expected:

```bash
nitric start
```

## Building the Profile API

Applications built with Nitric can contain many APIs, let's start by adding an API and a key value store to this project to serve as the public endpoint.

```go title:services/hello/main.go
import (
  "github.com/nitrictech/go-sdk/nitric"
  "github.com/nitrictech/go-sdk/nitric/keyvalue"
)

func main() {
  profilesApi := nitric.NewApi("public")

  profiles := nitric.NewKv("profiles").Allow(keyvalue.KvStoreGet, keyvalue.KvStoreSet)

  nitric.Run()
}
```

Here we're creating:

- An API named `public`,
- A key value store named `profiles` and giving our function permission to read and write to that key value store.

From here, let's add some features to that function that allow us to work with profiles.

<Note>
  You could separate some or all of these request handlers into their own
  services if you prefer. For simplicity we'll group them together in this
  guide.
</Note>

### Create profiles with POST

```go title:services/hello/main.go
profilesApi.Post("/profiles", func(ctx *apis.Ctx) error {
  id := uuid.New().String()

  var profileRequest map[string]interface{}
  err := json.Unmarshal(ctx.Request.Data(), &profileRequest)
  if err != nil {
    return err
  }

  err = profiles.Set(context.Background(), id, profileRequest)
  if err != nil {
    return err
  }

  ctx.Response.Body = []byte(id)
  return nil
})
```

### Retrieve a profile with GET

```go title:services/hello/main.go
profilesApi.Get("/profiles/:id", func(ctx *apis.Ctx) {
  id := ctx.Request.PathParams()["id"]

  profile, err := profiles.Get(context.Background(), id)
  if err != nil {
    ctx.Response.Status = 404
    ctx.Response.Body = []byte(fmt.Sprintf("profile with id '%s' not found", id))

    return
  }

  ctx.Response.Body, err = json.Marshal(profile)
})
```

### List all profiles with GET

```go title:services/hello/main.go
profilesApi.Get("/profiles", func(ctx *apis.Ctx) error {
  keys, err := profiles.Keys(context.TODO())
  if err != nil {
    return err
  }

  var profileContent []map[string]interface{}
  for {
    key, err := keys.Recv()
    if err != nil {
      break
    }
    content, _ := profiles.Get(context.Background(), key)
    profileContent = append(profileContent, content)
  }

  ctx.Response.Body, err = json.Marshal(profileContent)

  return err
})
```

### Remove a profile with DELETE

```go title:services/hello/main.go
profilesApi.Delete("/profiles/:id", func(ctx *apis.Ctx) {
  id := ctx.Request.PathParams()["id"]

  err := profiles.Delete(context.Background(), id)
  if err != nil {
    ctx.Response.Status = 404
    ctx.Response.Body = []byte(fmt.Sprintf("profile with id '%s' not found", id))

    return
  }
})
```

Do a quick `go mod tidy` to make sure all new dependencies are resolved.

## Ok, let's run this thing!

Now that you have an API defined with handlers for each of its methods, it's time to test it locally.

```bash
nitric start
```

Once it starts, the application will receive requests via the API port. You can use cURL, Postman or any other HTTP client to test the API.

We will keep it running for our tests. If you want to update your services, just save them, they'll be reloaded automatically.

## Test your API

Update all values in brackets `[]` and change the URL to your deployed URL if you're testing on the cloud.

### Create Profile

```bash
curl --location --request POST 'http://localhost:4001/profiles' \
--header 'Content-Type: text/plain' \
--data-raw '{
    "name": "Peter Parker",
    "age": "21",
    "homeTown" : "Queens"
}'
```

### Fetch Profile

```bash
curl --location --request GET 'http://localhost:4001/profiles/[id]'
```

### Fetch All Profiles

```bash
curl --location --request GET 'http://localhost:4001/profiles'
```

### Delete Profile

```bash
curl --location --request DELETE 'http://localhost:4001/profiles/[id]'
```

## Deploy to the cloud

At this point, you can deploy what you've built to any of the supported cloud providers. To do this start by setting up your credentials and any configuration for the cloud you prefer:

- [AWS](/providers/pulumi/aws)
- [Azure](/providers/pulumi/azure)
- [GCP](/providers/pulumi/gcp)

Next, we'll need to create a `stack`. A stack represents a deployed instance of an application, which is a key value store of resources defined in your project. You might want separate stacks for each environment, such as stacks for `dev`, `test` and `prod`. For now, let's start by creating a `dev` stack.

The `stack new` command below will create a stack named `dev` that uses the `aws` provider.

```bash
nitric stack new dev aws
```

Continue by checking your stack file `nitric.dev.yaml` and adding in your preferred region, let's use `us-east-1`.

```yaml
# The nitric provider to use
provider: nitric/aws@latest
# The target aws region to deploy to
# See available regions:
# https://docs.aws.amazon.com/general/latest/gr/lambda-service.html
region: us-east-1
```

<Note>
  Cloud deployments incur costs and while most of these resource are available
  with free tier pricing you should consider the costs of the deployment.
</Note>

We called our stack `dev`, let's try deploying it with the `up` command

```bash
nitric up
```

When the deployment is complete, go to the relevant cloud console and you'll be able to see and interact with your API.

To tear down your application from the cloud, use the `down` command:

```bash
nitric down
```

## Optional - Add profile image upload/download support

If you want to go a bit deeper and create some other resources with Nitric, why not add images to your profiles API.

### Access profile buckets with permissions

Define a bucket named `profileImages` with read/write permissions

```go title:services/hello/main.go
profileImages := nitric.NewBucket("profileImages").Allow(storage.BucketRead, storage.BucketWrite)
```

### Get a URL to upload a profile image

```go title:services/hello/main.go
profilesApi.Get("/profiles/:id/image/upload", func(ctx *apis.Ctx) error {
  id := ctx.Request.PathParams()["id"]
  photoId := fmt.Sprintf("images/%s/photo.png", id)

  photoUrl, err := profileImages.UploadUrl(context.TODO(), photoId, storage.WithPresignUrlExpiry(600))
  if err != nil {
    return err
  }

  ctx.Response.Body = []byte(photoUrl)

  return nil
})
```

### Get a URL to download a profile image

```go title:services/hello/main.go
profilesApi.Get("/profiles/:id/image/download", func(ctx *apis.Ctx) error {
  id := ctx.Request.PathParams()["id"]
  photoId := fmt.Sprintf("images/%s/photo.png", id)

  photoUrl, err := profileImages.DownloadUrl(context.TODO(), photoId, storage.WithPresignUrlExpiry(600))
  if err != nil {
    return err
  }

  ctx.Response.Body = []byte(photoUrl)

  return nil
})
```

You can also directly redirect to the photo URL.

```go title:services/hello/main.go
profilesApi.Get("/profiles/:id/image/view", func(ctx *apis.Ctx) error {
  id := ctx.Request.PathParams()["id"]
  photoId := fmt.Sprintf("images/%s/photo.png", id)

  photoUrl, err := profileImages.DownloadUrl(context.TODO(), photoId, storage.WithPresignUrlExpiry(600))
  if err != nil {
    return err
  }

  ctx.Response.Status = 303
  ctx.Response.Headers["Location"] = []string{photoUrl}

  return nil
})
```

#### Time to test the updated API

Update all values in brackets `[]` and change the URL to your deployed URL if you're testing on the cloud.

#### Get an image upload URL

```bash
curl --location --request GET 'http://localhost:4001/profiles/[id]/image/upload'
```

#### Using the upload URL with curl

```bash
curl --location --request PUT '[url]' \
--header 'content-type: image/png' \
--data-binary '@/home/user/Pictures/photo.png'

```

#### Get an image download URL

```bash
curl --location --request GET 'http://localhost:4001/profiles/[id]/image/download'
```
